Transfer GFF to GTF file

GFF 转换为 GTF 文件

Annotation
Author

Jie Hua

Published

Apr 29, 2025

Modified

Apr 29, 2025

基本信息 / Basic Information

GFF3(Generic Feature Format Version 3)文件格式以简单的纯文本、制表符分隔的文件来描述基因组特征

GFF3 文件包含九个字段(seqid、source、feature、start、end、score、strand、phase 和 attributes)

以“##”开头的行提供文件的元信息,以“#”开头则表示人类可读的注释

GFF3 (Generic Feature Format Version 3) file format represents the genomic features in a simple text-based tab-delimited file

GFF3 file has nine fields (seqid, source, feature, start, end, score, strand, phase, and attributes)

The lines which starts with ‘##’ provides the meta-information of the file and ‘#’ represents the human-readable comments

我们有时需要把 GFF3 转换为 GTF 格式。

We sometimes need to transfer GFF3 to GTF format.

我之前使用过 gffread。它通常运行良好,但当我要处理从 NCBI 下载的 GFF3 文件时,会出现一些问题。

I used gffread before. It generally works well, but it has some problem when I need to deal with a GFF3 file downloaded from NCBI.

这个文件中有一些类似下面的行:

This file has some lines like:

chrxxx  Gnomon  exon    46964   46999   .   -   .   ID=id-LOC123327042;Parent=gene-LOC123327042;Dbxref=GeneID:123327042;gbkey=exon;gene=LOC123327042;model_evidence=Supporting evidence includes similarity to: 1 Protein%2C and 74%25 coverage of the annotated genomic feature by RNAseq alignments;pseudo=true
chrxxx  Gnomon  exon    47054   47468   .   -   .   ID=id-LOC123327042-2;Parent=gene-LOC123327042;Dbxref=GeneID:123327042;gbkey=exon;gene=LOC123327042;model_evidence=Supporting evidence includes similarity to: 1 Protein%2C and 74%25 coverage of the annotated genomic feature by RNAseq alignments;pseudo=true
chrxxx  Gnomon  exon    47542   47661   .   -   .   ID=id-LOC123327042-3;Parent=gene-LOC123327042;Dbxref=GeneID:123327042;gbkey=exon;gene=LOC123327042;model_evidence=Supporting evidence includes similarity to: 1 Protein%2C and 74%25 coverage of the annotated genomic feature by RNAseq alignments;pseudo=true

当我使用 gffread 时,它无法读取那些包含 “ID=id*” 的行,输出的 GTF 文件中会丢失 gene_id 属性。

When I used gffread, it can not read there lines containing “ID=id*”, it will lose the gene_id attribute in the output GTF file.

我意识到需要自己写一个脚本。

I realized that I need to write a script by myself.

脚本 / Script

import sys
import uuid
import re

def parse_gff_attributes(attributes_str):
    """Parse GFF attributes into a dictionary."""
    attributes = {}
    for attr in attributes_str.split(';'):
        if attr:
            key_value = attr.split('=', 1)
            if len(key_value) == 2:
                key, value = key_value
                attributes[key] = value
    return attributes

def convert_gff_to_gtf(gff_file, gtf_file):
    """Convert GFF3 to GTF format, retaining ID, gene_id, and transcript_id."""
    with open(gff_file, 'r') as gff, open(gtf_file, 'w') as gtf:
        for line in gff:
            if line.startswith('#'):
                gtf.write(line)
                continue
            
            fields = line.strip().split('\t')
            if len(fields) != 9:
                continue
                
            seqid, source, feature, start, end, score, strand, phase, attributes_str = fields
            attributes = parse_gff_attributes(attributes_str)
            
            # Skip features that are not relevant for GTF (e.g., region)
            if feature not in ['gene', 'mRNA', 'exon', 'CDS', 'lnc_RNA', 'pseudogene']:
                continue
                
            # Determine feature type for GTF
            gtf_feature = feature
            if feature == 'mRNA' or feature == 'lnc_RNA':
                gtf_feature = 'transcript'
            
            # Extract required attributes
            gene_id = attributes.get('gene', '')
            transcript_id = attributes.get('transcript_id', '')
            feature_id = attributes.get('ID', '')
            
            # Build GTF attributes string
            gtf_attributes = []
            if feature == 'gene':
                gtf_attributes.append(f'gene_id "{gene_id}"')
                if 'Name' in attributes:
                    gtf_attributes.append(f'gene_name "{attributes["Name"]}"')
            elif feature in ['mRNA', 'lnc_RNA', 'exon', 'CDS']:
                gtf_attributes.append(f'gene_id "{gene_id}"')
                gtf_attributes.append(f'transcript_id "{transcript_id}"')
                if 'Name' in attributes and feature in ['mRNA', 'lnc_RNA']:
                    gtf_attributes.append(f'gene_name "{attributes["Name"]}"')
                if feature == 'exon':
                    gtf_attributes.append(f'exon_id "{feature_id}"')
            elif feature == 'pseudogene':
                gtf_attributes.append(f'gene_id "{gene_id}"')
                if 'Name' in attributes:
                    gtf_attributes.append(f'gene_name "{attributes["Name"]}"')
            
            # Write GTF line
            gtf_fields = [seqid, source, gtf_feature, start, end, score, strand, phase, '; '.join(gtf_attributes)]
            gtf.write('\t'.join(gtf_fields) + '\n')

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: python gff_to_gtf.py input.gff output.gtf")
        sys.exit(1)
    
    gff_file = sys.argv[1]
    gtf_file = sys.argv[2]
    convert_gff_to_gtf(gff_file, gtf_file)
    print(f"Conversion complete. GTF file written to {gtf_file}")

使用方法 / Usage

使用这个脚本非常简单,只需将其保存为 gff2gtf.py,然后按如下方式运行

It is very easy to use this script, just save it as gff2gtf.py and then run like

python gff2gtf.py input.gff output.gtf

结果如下所示

The result looks like

chrxxx      Gnomon  exon    46964   46999   .       -       .       gene_id "LOC123327042"; transcript_id ""; exon_id "id-LOC123327042"
chrxxx      Gnomon  exon    47054   47468   .       -       .       gene_id "LOC123327042"; transcript_id ""; exon_id "id-LOC123327042-2"
chrxxx       Gnomon  exon    47542   47661   .       -       .       gene_id "LOC123327042"; transcript_id ""; exon_id "id-LOC123327042-3"